package com.mileoresko.hsm.test;

import java.util.HashMap;

import junit.framework.TestCase;

import com.mileoresko.hsm.test.machines.seminar.Course;
import com.mileoresko.hsm.test.machines.seminar.Enrollment;
import com.mileoresko.hsm.test.machines.seminar.FakeNames;
import com.mileoresko.hsm.test.machines.seminar.Student;
import com.mileoresko.hsm.test.machines.seminar.Course.CourseEnrollmentState;
import com.mileoresko.hsm.test.machines.seminar.Course.CourseEvents;
import com.mileoresko.hsm.test.machines.seminar.Enrollment.EnrollmentEvents;

public class HSMTestEnrollment extends TestCase {

    private static final int MAX_GROUP_SIZE = 100;
    private static final int MAX_WAITING_LIST_SIZE = 20;

    private HashMap bigEnrollmentHsms;

    private static final String COURSE_NAME = "History Matters, by Pine Apple";
    private static final String[] COURSES = { "History Matters, by Pine Apple", "Solidified experts, by Jack Rabbit",
            "Ethernal Fire, by Stone Cold" };
    private static final String[] DATES = { "October 11th, 2011", "March 25th, 2011", "June 1st, 2011" };

    public HSMTestEnrollment(final String arg0) {
        super(arg0);
    }

    protected void setUp() throws Exception {
        super.setUp();
        bigEnrollmentHsms = new HashMap(COURSES.length);
        for (int i = 0; i < COURSES.length; i++) {
            bigEnrollmentHsms.put(COURSES[i], new Enrollment(COURSES[i], MAX_GROUP_SIZE, MAX_WAITING_LIST_SIZE));
        }
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testEnrollmentsBig() {
        createAndStartEnrollments();
        enrollAndDrop(1700);
        closeAndHaltEnrollments();
    }

    public void testEnrollmentSmall() {
        Enrollment enrollment;
        enrollment = new Enrollment(COURSE_NAME, 6, 3);
        enrollment.start(null);
        enrollment.pushEvent(EnrollmentEvents.SCHEDULE, "October 11th, 2011");
        enrollment.pushEvent(EnrollmentEvents.OPEN, null);
        assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());

        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mrs. Apple Pie"));
        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mr. Coal Mine"));
        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Cool Aid, Phd."));
        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Serious Serial Sam"));
        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Johnny Walker III"));
        assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());

        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mrs. Loud Mouth"));
        assertEquals(enrollment.states().FULL_TO_CAPACITY, enrollment.getCurrentState());

        enrollment.pushEvent(EnrollmentEvents.DROP_STUDENT, new Student("Johnny Walker III"));
        assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());

        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mr. Hope Not"));
        assertEquals(enrollment.states().FULL_TO_CAPACITY, enrollment.getCurrentState());

        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mintberry Crunch"));
        enrollment.pushEvent(EnrollmentEvents.DROP_STUDENT, new Student("Cool Aid, Phd."));
        assertEquals(enrollment.states().FULL_TO_CAPACITY, enrollment.getCurrentState());

        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mr. Simon Says"));
        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mrs. Simona Says"));
        assertEquals(enrollment.states().FULL_TO_CAPACITY, enrollment.getCurrentState());
        assertEquals(1, enrollment.getCoursesCount());

        enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Sam Says"));
        assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());
        assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());
        assertEquals(2, enrollment.getCoursesCount());

        enrollment.pushEvent(EnrollmentEvents.CLOSE, null);
        assertEquals(enrollment.states().CLOSED_FOR_ENROLLMENT, enrollment.getCurrentState());

        enrollment.halt(null);
        assertEquals(false, enrollment.isRunning());
    }

    public void testOneCourse() {
        Course course = new Course(COURSE_NAME, MAX_GROUP_SIZE, MAX_WAITING_LIST_SIZE);
        course.start(null);

        course.pushEvent(EnrollmentEvents.SCHEDULE, "October 11th, 2011");
        course.pushEvent(EnrollmentEvents.OPEN, null);
        assertEquals(course.states().ENROLLMENT, course.getCurrentState());

        Enrollment nestedEnrollment = ((CourseEnrollmentState) course.getCurrentState()).getNestedEnrollmentHsm();
        assertEquals(nestedEnrollment.states().OPEN_FOR_ENROLLMENT, nestedEnrollment.getCurrentState());

        course.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student("Mr. Coal Mine"));
        assertEquals(nestedEnrollment.states().OPEN_FOR_ENROLLMENT, nestedEnrollment.getCurrentState());
        
        course.pushEvent(CourseEvents.TERM_START, null);
        assertEquals(nestedEnrollment.states().CLOSED_FOR_ENROLLMENT, nestedEnrollment.getCurrentState());
        assertEquals(course.states().LECTURES, course.getCurrentState());
        
        course.pushEvent(CourseEvents.LECTURES_OVER, null);
        assertEquals(course.states().FINAL_EXAMS, course.getCurrentState());
        
        course.pushEvent(CourseEvents.EXAMS_OVER, null);
        assertEquals(course.states().COMPLETE, course.getCurrentState());
        
        course.halt(null);
    }

    private void closeAndHaltEnrollments() {
        for (int i = 0; i < COURSES.length; i++) {
            final Enrollment enrollment = (Enrollment) bigEnrollmentHsms.get(COURSES[i]);
            enrollment.pushEvent(EnrollmentEvents.CLOSE, null);
            assertEquals(enrollment.states().CLOSED_FOR_ENROLLMENT, enrollment.getCurrentState());
            enrollment.halt(null);
            assertEquals(false, enrollment.isRunning());
        }
    }

    private void createAndStartEnrollments() {
        for (int i = 0; i < COURSES.length; i++) {
            final Enrollment enrollment = (Enrollment) bigEnrollmentHsms.get(COURSES[i]);
            enrollment.start(null);
            enrollment.pushEvent(EnrollmentEvents.SCHEDULE, DATES[i]);
            enrollment.pushEvent(EnrollmentEvents.OPEN, null);
            assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());
        }
    }

    private void enrollAndDrop(final int studentsCount) {
        for (int k = 1; k <= 1700; k++) {
            for (int i = 0; i < COURSES.length; i++) {
                final Enrollment enrollment = (Enrollment) bigEnrollmentHsms.get(COURSES[i]);
                enrollment.pushEvent(EnrollmentEvents.ENROLL_STUDENT, new Student(FakeNames.NAMES[k]));
                if (k > enrollment.getMaxWaitingListSize()
                        && k % enrollment.getMaxGroupSize() < enrollment.getMaxWaitingListSize()) {
                    // there should be a non-empty waiting list here
                    assertEquals(enrollment.states().FULL_TO_CAPACITY, enrollment.getCurrentState());
                    assertEquals(k % enrollment.getMaxGroupSize(), enrollment.getWaitingListCount());
                    assertEquals(enrollment.getCoursesCount() * enrollment.getMaxGroupSize(),
                            enrollment.getEnrolledCount());
                    assertEquals(k / enrollment.getMaxGroupSize(), enrollment.getCoursesCount());
                } else {
                    // there should be no(!) waiting list here
                    assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());
                    assertEquals(0, enrollment.getWaitingListCount());
                    assertEquals(k, enrollment.getEnrolledCount());
                    assertEquals(1 + k / enrollment.getMaxGroupSize(), enrollment.getCoursesCount());
                }
            }
        }
        for (int k = 1700; k >= 1; k--) {
            for (int i = 0; i < COURSES.length; i++) {
                final Enrollment enrollment = (Enrollment) bigEnrollmentHsms.get(COURSES[i]);
                if (k > enrollment.getMaxWaitingListSize()
                        && k % enrollment.getMaxGroupSize() < enrollment.getMaxWaitingListSize()) {
                    // there should be a non-empty waiting list here
                    assertEquals(enrollment.states().FULL_TO_CAPACITY, enrollment.getCurrentState());
                    assertEquals(k % enrollment.getMaxGroupSize(), enrollment.getWaitingListCount());
                    assertEquals(enrollment.getCoursesCount() * enrollment.getMaxGroupSize(),
                            enrollment.getEnrolledCount());
                    assertEquals(k / enrollment.getMaxGroupSize(), enrollment.getCoursesCount());
                } else {
                    // there should be no(!) waiting list here
                    assertEquals(enrollment.states().OPEN_FOR_ENROLLMENT, enrollment.getCurrentState());
                    assertEquals(0, enrollment.getWaitingListCount());
                    assertEquals(k, enrollment.getEnrolledCount());
                    assertEquals(1 + k / enrollment.getMaxGroupSize(), enrollment.getCoursesCount());
                }
                enrollment.pushEvent(EnrollmentEvents.DROP_STUDENT, new Student(FakeNames.NAMES[k]));
            }
        }
    }
}
